package com.zkh.myutils.markup;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import com.zkh.myutils.utils.Assert;
import com.zkh.myutils.utils.StringUtils;

/**
 * 元素对象。针对标记语言如XML和HTML，用来代表一个节点。其中，文本也记为一个元素
 */
public class Element {
	/**
	 * 文本节点名称
	 */
	public final static String ELEMENT_TEXT = "#TEXT"; 
	/**
	 * 元素名称
	 */
	private String name;
	/**
	 * 元素属性
	 */
	private Map<CaseInsensitiveKey, String> attrMap = new LinkedHashMap<>();
	/**
	 * 文本内容
	 */
	private String text;
	/**
	 * 父级元素
	 */
	private Element parent;
	/**
	 * 没有标签体
	 */
	private boolean noTagBody;
	/**
	 * 子元素
	 */
	private List<Element> children = new ArrayList<>();
	
	/**
	 * 构造方法
	 * @param name 元素名称
	 */
	public Element(String name) {
		this.name = name;
	}
	
	/* 属性操作 */
	/**
	 * 获取属性值
	 * @param attrName 属性名称
	 */
	public String getAttr(String attrName) {
		return getAttr(attrName, false);
	}
	/**
	 * 获取属性值
	 * @param attrName 属性名称
	 * @param replaceSpecialChar 是否替换特殊字符。如：&amp;lt;等
	 */
	public String getAttr(String attrName, boolean replaceSpecialChar) {
		//值
		String value = attrMap.get(new CaseInsensitiveKey(attrName));
		//替换特殊字符
		return replaceSpecialChar ? replaceSpecialChar(value) : value;
	}
	/**
	 * 保存属性（属性值设置为null）
	 * @param attrName 属性名称
	 */
	public void putAttr(String attrName) {
		attrMap.put(new CaseInsensitiveKey(attrName), null);
	}
	/**
	 * 保存属性值
	 * @param attrName 属性名称
	 * @param attrValue 属性值
	 */
	public void putAttr(String attrName, String attrValue) {
		attrMap.put(new CaseInsensitiveKey(attrName), attrValue);
	}
	/**
	 * 保存属性值
	 * @param attrs 属性Map
	 */
	public void putAttrs(Map<String, String> attrs) {
		attrs.forEach((k, v)->attrMap.put(new CaseInsensitiveKey(k), v));
	}
	/**
	 * 是否存在指定属性
	 * @param attrName 属性名称
	 */
	public boolean hasAttr(String attrName) {
		return attrMap.containsKey(new CaseInsensitiveKey(attrName));
	}
	/**
	 * 移除指定属性
	 * @param attrName 属性名称
	 * @return 被移除属性的值
	 */
	public String removeAttr(String attrName) {
		return attrMap.remove(new CaseInsensitiveKey(attrName));
	}
	/**
	 * 清除属性
	 */
	public void clearAttr() {
		attrMap.clear();
	}
	/**
	 * 获取属性拼接字符串
	 */
	public String getAttrsString() {
		return getAttrsString(false);
	}
	
	/**
	 * 获取属性拼接字符串
	 * @param replaceSpecialChar 是否替换特殊字符
	 */
	private String getAttrsString(boolean replaceSpecialChar) {
		//属性集合
		List<String> attrs = new ArrayList<>();
		//遍历属性（无属性值的，自动将属性值设置为key）
		attrMap.forEach((k,v)->attrs.add(k.originKey+"=\""+(v==null?k.originKey:v).replace("\"", "&quot;")+"\""));
		//结果
		String res = attrs.isEmpty() ? "" : " " + String.join(" ", attrs);
		//替换特殊字符
		if(replaceSpecialChar && !res.isEmpty()) {
			res = replaceSpecialChar(res);
		}
		//拼接
		return res;
	}
	
	/* * 子节点操作 **/
	/**
	 * 判断是否存在子节点
	 */
	public boolean hasChild() {
		return !children.isEmpty();
	}
	public Element getChild(int index) {
		return children.get(index);
	}
	public Element firstChild() {
		return children.isEmpty() ? null : children.get(0);
	}
	public Element lastChild() {
		return children.isEmpty() ? null : children.get(children.size()-1);
	}
	public void addChild(Element child) {
		child.setParent(this);
		children.add(child);
	}
	public void addChild(Element child, int index) {
		child.setParent(this);
		children.add(index, child);
	}
	public Element removeChild(int index) {
		return children.remove(index);
	}
	public Element removeChild(Element element) {
		for(int i=0,len=children.size(); i<len; i++) {
			if(children.get(i)==element) {
				return children.remove(i);
			}
		}
		return null;
	}
	public void clearChildren() {
		children.clear();
	}
	public void replaceChild(Element oldElement, Element newElement) {
		for(int i=0,len=children.size(); i<len; i++) {
			if(children.get(i)==oldElement) {
				newElement.setParent(this);
				children.set(i, newElement);
			}
		}
	}
	public void replaceChild(int index, Element newElement) {
		newElement.setParent(this);
		children.set(index, newElement);
	}
	/* * 节点操作 **/
	public Element prev() {
		return prev(1);
	}
	public Element prev(int n) {
		return findSibling(n, true);
	}
	public Element next() {
		return next(1);
	}
	public Element next(int n) {
		return findSibling(n, false);
	}
	private Element findSibling(int n, boolean forward) {
		Assert.ifTrueThrow(n<1, "向"+(forward?"前":"后")+"查找位置不能小于1");
		requiredParent();
		//子元素
		List<Element> cs = parent.getChildren();
		//遍历
		for(int i=0,len=cs.size(); i<len; i++) {
			//找到当前
			if(cs.get(i)==this) {
				//指定位置
				int index = forward ? i-n : i+n;
				//指定位置有元素
				if(forward ? index>-1 : index<len) {
					return cs.get(index);
				}
				break;
			}
		}
		return null;
	}
	public void addBefore(Element element) {
		addSibling(element, true);
	}
	public void addAfter(Element element) {
		addSibling(element, false);
	}
	private void addSibling(Element element, boolean before) {
		requiredParent();
		element.setParent(parent);
		//遍历
		for(int i=0,len=parent.children.size(); i<len; i++) {
			//找到当前
			if(parent.children.get(i)==this) {
				parent.addChild(element, before ? i : i+1);break;
			}
		}
	}
	
	/**
	 * 移除当前节点
	 */
	public void remove() {
		//有父节点
		if(parent!=null) {
			for(int i=0,len=parent.children.size(); i<len; i++) {
				if(parent.children.get(i)==this) {
					parent.removeChild(i);
				}
			}
		}
	}


	/**
	 * 是否是文本节点
	 */
	public boolean isText() {
		return ELEMENT_TEXT.equals(name);
	}
	/**
	 * 父元素不能为null
	 */
	private void requiredParent() {
		if(parent==null) {
			throw new MarkupException("父元素为空");
		}
	}
	
	/* * 查找元素 **/
	/**
	 * 根据ID查找元素
	 * @param id 元素ID
	 */
	public Element findById(String id) {
		return findById(id, false);
	}
	/**
	 * 根据ID查找元素
	 * @param id 元素ID
	 * @param excludeSelf 是否排除当前元素本身
	 */
	public Element findById(String id, boolean excludeSelf) {
		//不为空
		Objects.requireNonNull(id);
		//判断本身
		if(!excludeSelf && id.equals(getAttr("id"))) {
			return this;
		}
		//遍历子元素
		for(Element c: children) {
			//非文本
			if(!isText()) {
				//找到ID
				if(id.equals(c.getAttr("id"))) {
					return c;
				}
				//查找子元素
				Element elem = c.findById(id);
				//不为空
				if(elem!=null) {
					return elem;
				}
			}
		}
		//未找到
		return null;
	}
	
	/**
	 * 根据标签名查询元素集合
	 * @param tagName 标签名
	 */
	public List<Element> findByTagName(String tagName){
		return findByTagName(tagName, false);
	}
	
	/**
	 * 根据标签名查询元素集合
	 * @param tagName 标签名
	 */
	public List<Element> findByTagName(String tagName, boolean excludeSelf) {
		//为空
		if(StringUtils.isEmpty(tagName)) {
			throw new IllegalArgumentException("标签名不能为空");
		}
		//结果容器
		List<Element> resList = new ArrayList<>();
		//判断当前元素
		if(!excludeSelf && tagName.equalsIgnoreCase(name)) {
			resList.add(this);
		}
		findByTagName(this, tagName, resList);
		//返回
		return resList;
	}
	
	/**
	 * 根据标签名查询元素集合
	 * @param elem 父元素
	 * @param tagName 查找标签名
	 * @param resList 结果容器
	 */
	private void findByTagName(Element elem, String tagName, List<Element> resList) {
		//遍历查询
		for(Element t: elem.getChildren()) {
			//非文本元素，则处理
			if(!t.isText()) {
				//判断标签名
				if(tagName.equalsIgnoreCase(t.getName())) {
					resList.add(t);
				}
				//继续处理子元素
				findByTagName(t, tagName, resList);
			}
		}	
	}
	
	/**
	 * 根据CSS选择器查找元素
	 * @param selector CSS选择器。
	 * <pre>
	 * 支持选择器：
	 * 一）基础选择器
	 * 	1、通用选择器（*）。如：div *
	 *  2、元素选择器（标签）。如：p，div
	 *  3、类选择器（.）。如：.title
	 *  4、ID选择器（#）。如：#title
	 *  5、属性选择器（[属性]或[属性=属性值]）。如：a[href]，p[title=test]
	 * 二）组合选择器
	 *  1、后代选择器。如：div p
	 *  2、子代选择器（>）。如：div>p
	 *  3、相邻选择器（+）。如：h2+p
	 *  4、通用选择器（~）。如：p~ul（查找p元素后的所有元素）
	 * 三）伪类选择器
	 *  1、开始、末尾（:first-child、:last-child）
	 *  2、指定位置（:nth-child(kn+b)，其中，k、b为常数，k为倍数，b为偏移量，n为固定变量字符，公式kn+b包含以下几种情况）
	 *  	a) 当k=0时，选择器简写为:nth-child(b)，b指定具体位置。如：b为2时，:nth-child(2)代表取第二个子元素。
	 *  	b) 当k=±1时，选择器为:nth-child(n+b)或:nth-child(-n+b)，直接用公式计算出结果元素的位置，如果结果元素存在，则取用。
	 *          如：ul>li*7，选择器为:ul li:nth-child(n+4)，处理第1个li时，计算公式1+4=5，第5个元素存在，则取用第5个，...，处理第4个li时，
	 *          计算公式4+4=8，第8个元素不存在，则忽略，即n+4实际作用为取位置大于4的元素。
	 *          当选择器为：ul li:nth-child(-n+4)，同理，处理第一个li时，计算公式-1x1+4=3，第3个元素存在，则取用第3个，...，处理第4个li时，
	 *          计算公式-1x4+4=0，第0个元素不存在，则忽略，即-n+4实际作用为取位置小于4的元素。
	 *  	c) 当k>1时，代表用当前位置偏移后，如果是k的倍数，则取用当前位置元素。b为正，代表向后偏移，b为负，代表向前偏移。
	 *          如：ul>li*7，选择器为：ul li:nth-child(3n+1)，处理第1个li时，先向后偏移1，即为2，2不是3的倍数，不取用第1个元素；处理第2个li时，
	 *          先向后偏移1，即为3，3是3的倍数，则取用第2个元素。
	 *          当选择器为：ul li:nth-child(3n-1)，处理第1个li时，先向前偏移1，即为0，0是3的倍数，取用第1个元素；处理第2个li时，
	 *          先向前偏移1，即为1，1不是3的倍数，则不取用第2个元素。
	 * </pre>
	 * @return 查找到的元素集合
	 */
	public List<Element> findBySelector(String selector) {
		return findBySelector(selector, false);
	}
	
	/**
	 * 根据CSS选择器查找元素
	 * @param selector CSS选择器。
	 * @param cacheSelector 是否缓存解析后的选择器
	 */
	public List<Element> findBySelector(String selector, boolean cacheSelector) {
		if(StringUtils.isEmpty(selector)) {
			throw new IllegalArgumentException("选择器不能为空");
		}
		//查找
		return SelectorFindor.find(this, selector, cacheSelector);
	}
	
	/**
	 * 文本toString
	 */
	public String toTextString() {
		//文本
		if(isText() || StringUtils.isNotEmpty(text)) {
			return text;
		}
		//结果
		StringBuilder sb = new StringBuilder();
		//子节点
		children.forEach(elem->Optional.ofNullable(elem.toTextString()).ifPresent(sb::append));
		//返回
		return sb.toString();
	}

	/**
	 * 元素toString（包含节点和属性）
	 */
	public String toMarkupString() {
		//文本
		if(isText()) {
			return text;
		}
		//结果
		StringBuilder sb = new StringBuilder();
		//没有结束标签的元素
		if(noTagBody) {
			sb.append('<').append(name).append(getAttrsString()).append("/>");
		}else {
			//开始标签+属性
			sb.append('<').append(name).append(getAttrsString()).append(">");
			//有文本内容
			if(StringUtils.isNotEmpty(text)) {
				sb.append(text);
			}
			//子节点
			children.forEach(elem->Optional.ofNullable(elem.toMarkupString()).ifPresent(sb::append));
			//结束标签
			sb.append("</").append(name).append('>');
		}
		//返回
		return sb.toString();
	}
	
	/**
	 * 内部元素toString（包含节点和属性）
	 */
	public String toInnerMarkupString() {
		//文本节点或无标签体
		if(isText() || noTagBody) {
			return "";
		}
		//子节点
		if(StringUtils.isEmpty(text)) {
			//结果
			StringBuilder sb = new StringBuilder();
			//子节点
			children.forEach(elem->Optional.ofNullable(elem.toMarkupString()).ifPresent(sb::append));
			//返回
			return sb.toString();
		}
		//返回文本
		return text;
	}
	
	/* * GETTER和SETTER **/
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Map<String, String> getAttrMap() {
		Map<String, String> map = new LinkedHashMap<>();
		attrMap.forEach((k,v)->map.put(k.originKey, v));
		return map;
	}
	public String getText() {
		return text;
	}
	public String getText(boolean replaceSpecialChar) {
		//替换特殊字符
		if(replaceSpecialChar) {
			return replaceSpecialChar(text);
		}
		return text;
	}
	public void setText(String text) {
		this.text = text;
	}
	/**
	 * 替换特殊字符
	 * @param string 待替换字符
	 */
	private String replaceSpecialChar(String string) {
		if(string==null) {
			return null;
		}
		return string.replace("&gt;", ">")
				.replace("&lt;", "<")
				.replace("&amp;", "&")
				.replace("&apos;", "'")
				.replace("&quot;", "\"")
				.replace("<![CDATA[", "")
				.replace("]]>", "");
	}
	public List<Element> getChildren() {
		return children;
	}
	public void setChildren(List<Element> children) {
		this.children = children;
	}
	public Element getParent() {
		return parent;
	}
	public void setParent(Element parent) {
		this.parent = parent;
	}
	public boolean isNoTagBody(){
		return noTagBody;
	}
	public Element setNoTagBody(boolean noTagBody) {
		this.noTagBody = noTagBody;return this;
	}
}

/**
 * Map的键查找时大小写不敏感，但使用时大小写敏感
 */
class CaseInsensitiveKey{
	//小写key
	String key;
	//原始key
	String originKey;
	/**
	 * 构造方法
	 * @param originKey 原始key
	 */
	public CaseInsensitiveKey(String originKey) {
		this.originKey = originKey;
		this.key = originKey.toLowerCase();
	}
	@Override
	public int hashCode() {
		return Objects.hash(key);
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || getClass() != obj.getClass())
			return false;
		CaseInsensitiveKey other = (CaseInsensitiveKey) obj;
		return Objects.equals(key, other.key);
	}
}
